#include <iostream>
#include <cstring>
#include <fstream>
#include <list>
#include <cmath>
#include <limits>
#include <iomanip>

#include "XMLRawParser.h"
#include "Arguments.h"
#include "Algorithm.h"

// ----------------------------------------------------------------------------

namespace gpxtools
{
  class GPXCat : public XMLParserHandler
  {
  public:
    // -- Constructor -----------------------------------------------------------
    GPXCat() :
      _arguments ("gpxcat [OPTION].. [FILE]\nConcat the track segments in a GPX-file.\n", "gpxcat v0.1",
                  "Concatenate the track segments in a GPX-file and display the resulting GPX-file on standard output."),
      _distance  (_arguments, true,  'd', "distance",   "METRES",            "concat only if both segments are less than METRES apart", ""),
      _xmlParser(this),
      _segmentDistance(-1.0),
      _doConcat(false)
    {
    }

    // -- Deconstructor ---------------------------------------------------------
    virtual ~GPXCat()
    {
    }


    bool processArguments(int argc, char *argv[])
    {
      std::vector<std::string> filenames;

      if (!_arguments.parse(argc,argv, filenames))
      {
        return false;
      }
      else if (!checkArguments(filenames))
      {
        return false;
      }
      else if (filenames.empty())
      {
        return _xmlParser.parse(std::cin);
      }
      else
      {
        return parseFile(filenames.front());
      }
    }

    // -- Check arguments ---------------------------------------------------------
    bool checkArguments(const std::vector<std::string> &filenames)
    {
      if (filenames.size() > 1)
      {
        std::cerr << "Too many input files."  << std::endl;
        return false;
      }

      if (!_distance.value().empty())
      {
        try
        {
          _segmentDistance = std::stoi(_distance.value());

          if (_segmentDistance < 0)
          {
            std::cerr << "Invalid value for --" << _distance.longOption() << " : " << _distance.value() << std::endl;
            return false;
          }
        }
        catch(...)
        {
          std::cerr << "Invalid value for --" << _distance.longOption() << " : " << _distance.value() << std::endl;
          return false;
        }
      }

      return true;
    }


    // -- Parse a file ----------------------------------------------------------
    bool parseFile(const std::string &filename)
    {
      bool ok =false;

      std::ifstream file(filename);

      if (file.is_open())
      {
        ok = _xmlParser.parse(file);

        file.close();
      }
      else
      {
        std::cerr << "Unable to open: " << filename << std::endl;
      }

      return ok;
    }

  private:
    void store(const std::string &text)
    {
      if (_doConcat)
      {
        _current.append(text);
      }
      else
      {
        std::cout << text;
      }
    }

    static bool getDoubleAttribute(const Attributes &atts, const std::string &key, double &value)
    {
      auto iter = atts.find(key);

      if (iter == atts.end()) return false;

      if (iter->second.empty()) return false;
      try
      {
        value = std::stod(iter->second);

        return true;
      }
      catch(...)
      {
        return false;
      }
    }

  public:
    // -- Callbacks -------------------------------------------------------------
    virtual void startElement(const std::string &path, const std::string &text, const std::string &, const Attributes &attributes)
    {
      if (path == "/gpx/trk/trkseg/trkpt")
      {
        double lat = 0.0, lon = 0.0;

        if (getDoubleAttribute(attributes, "lat", lat) && getDoubleAttribute(attributes, "lon", lon))
        {
          if (_doConcat)
          {
            if (_segmentDistance >= 0.0 && gpx::calcDistance(_lastLat, _lastLon, lat, lon) > _segmentDistance)
            {
              std::cout << _current;
            }

            _doConcat = false;
          }

          _lastLat = lat;
          _lastLon = lon;
        }
      }

      store(text);
    }

    virtual void text(const std::string &, const std::string &text)
    {
      store(text);
    }

    virtual void unhandled(const std::string &, const std::string &text)
    {
      store(text);
    }

    virtual void endElement(const std::string &path, const std::string &text, const std::string &name)
    {
      if (path == "/gpx/trk")
      {
        if (_doConcat)
        {
          std::cout << _current;

          _doConcat = false;
        }
      }
      else if (path == "/gpx/trk/trkseg")
      {
        _doConcat = true;
        _current.clear();
      }

      store(text);
    }

  private:
    // Members
    arg::Arguments    _arguments;

    arg::Argument     _distance;

    XMLRawParser      _xmlParser;

    double            _segmentDistance;

    std::string       _path;
    std::string       _current;
    double            _lastLat;
    double            _lastLon;
    bool              _doConcat;
  };
}

// -- Main program ------------------------------------------------------------

int main(int argc, char *argv[])
{
  gpxtools::GPXCat gpxCat;

  return gpxCat.processArguments(argc, argv) ? 0 : 1;
}
